﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;
using System.Reflection;
using System.Linq.Expressions;

namespace NMoq.Core
{
    public class MockProxy<T> : RealProxy
    {
        #region target properties 
        public T Target
        {
            get;
            private set;
        }

        public T ProxiedTarget
        {
            get { return (T)GetTransparentProxy(); }
        }
        #endregion

        #region extension properties
        /// <summary>
        /// Get or Set If read nothing from DataProvider , interceptor invoke RealMethod to 
        /// </summary>
        public InterceptorActionAfterRead ActionAfterRead
        { get; set; }
        #endregion

        #region constructors
        public MockProxy(T target)
            : base(typeof(T))
        {
            Target = target;
            AllActionSelector = new Dictionary<Type, object >();
            //InterceptorSelector = new InterceptorSelector();
            //DataProviderSelector = new DataProviderSelector();
            ActionAfterRead =  InterceptorActionAfterRead .None ;
        }

        public MockProxy(T target, InterceptorActionAfterRead action)
            :this(target )
        {
            ActionAfterRead = action;
        }

        #endregion

        #region selectors

        //public Selector<IInterceptor> InterceptorSelector
        //{ get; set; }

        //public Selector<IDataProvider> DataProviderSelector
        //{ get; set; }

        private Dictionary<Type, object> AllActionSelector
        { get; set; }

        public Selector<TAction> GetActionSelector<TAction>() where TAction : IAction 
        {
            if (!AllActionSelector.ContainsKey(typeof(TAction)))
            {
                AllActionSelector.Add(typeof(TAction), SelectorFactory .CreateSelector<TAction>() );
            }
            return AllActionSelector[typeof(TAction)] as Selector<TAction>;
        }

        public void SetActionSelector<TAction>(Selector<TAction> selector) where TAction : IAction
        {
            if (!AllActionSelector.ContainsKey(typeof(TAction)))
            {
                AllActionSelector.Add(typeof(TAction), selector as Selector<IAction>);
            }
            else
            {
                AllActionSelector[typeof(TAction)] = selector as Selector<IAction>;
            }
        }

        #endregion

        #region invoke
        public override System.Runtime.Remoting.Messaging.IMessage Invoke(System.Runtime.Remoting.Messaging.IMessage msg)
        {
            IMethodCallMessage callMessage = (IMethodCallMessage)msg;
            object retValue = null;
            IInvocation invocation = new Invocation() 
            {
                 Target = Target ,
                 ActionAfterRead =ActionAfterRead ,
                 MethodCallMessage = callMessage ,
                 ReturnResult = new DataProviderResult (),
                 HasIntercepted = false,
                 ShouldInvokeCall = false 
            };

            try
            {
                MethodMessageInfo methodMessageInfo = MethodMessageInfo.CreateMethodMessageInfo(callMessage, Target);

                IVerifier verifier = GetActionSelector<IVerifier>().Select(methodMessageInfo);
                bool verifierRes = false;
                if(verifier !=null )
                    verifierRes = verifier.Verify (methodMessageInfo );

                IInterceptor interceptor = GetActionSelector <IInterceptor>().Select(methodMessageInfo);

                IDataProvider dataProvider = null;
                // if the method should be intercepted , implemented by the interceptor
                if (interceptor != null)
                {
                    dataProvider = GetActionSelector<IDataProvider>().Select(methodMessageInfo);
                    interceptor.Intercept(invocation, dataProvider);
                }
                // if the method should not be intercepted, invoke origin method
                else
                {
                    invocation.ShouldInvokeCall = true;
                }

                retValue = invocation.ShouldInvokeCall 
                         ? callMessage.MethodBase.Invoke(Target, callMessage.Args) 
                         : invocation.ReturnResult.ReturnValue ;
                bool hasValue = invocation.ShouldInvokeCall ? true : invocation.ReturnResult.HasValue;

                invocation.ReturnResult = new DataProviderResult(hasValue, retValue); 

                if (interceptor != null)
                    interceptor.OnAftInvoked(invocation, dataProvider);

            }
            catch (Exception error)
            {
                return new ReturnMessage(error, callMessage);
            }

            return new ReturnMessage(retValue, new object[] { }, 0, null, callMessage);
        }
        #endregion

        #region selector methods
        /// <summary>
        /// Select the Selector<T1> in MockProxy,
        /// T1 should be IInterceptor , or IDataProvider
        /// </summary>
        /// <typeparam name="T1">T1 should be IInterceptor , or IDataProvider</typeparam>
        /// <returns></returns>
        //protected object GetSelector<T1>()  
        //    where T1:IAction 
        //{
        //    Type actionType = typeof (T1);
        //    if (typeof(IDataProvider).IsAssignableFrom(actionType))
        //        return DataProviderSelector;
        //    else if (typeof(IInterceptor).IsAssignableFrom(actionType))
        //        return InterceptorSelector;
        //    return null;
        //}

        /// <summary>
        /// Call the method of InterceptorSelector or DataProviderSelector
        /// </summary>
        /// <typeparam name="T1"></typeparam>
        /// <param name="expression"></param>
        public void CallSelector<T1>(Expression<Action<Selector<T1>>> expression)
            where T1 : IAction 
        {
            Action<Selector<T1>> action = expression.Compile();
            Selector<T1> selector = (Selector<T1>)GetActionSelector<T1>();
            if (selector != null)
                action.Invoke(selector);
        }

        /// <summary>
        /// Call the method of InterceptorSelector or DataProviderSelector
        /// </summary>
        /// <typeparam name="T1"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="expression"></param>
        /// <returns></returns>
        public TResult CallSelector<T1, TResult>(Expression<Func<Selector<T1>, TResult>> expression) 
            where T1 : IAction 
        {
            Func<Selector<T1>, TResult> func = expression.Compile();
            Selector<T1> selector = (Selector<T1>)GetActionSelector<T1>();
            if (selector != null)
                return func.Invoke(selector);
            return default (TResult );
        } 
        #endregion
    } 
}
